import CMDate from '../date/cmDate.js'

import Base from '../bagua/base.js'
import Gan from '../bagua/gan.js'
import Zhi from '../bagua/zhi.js'
import Xun from '../bagua/xun.js'
import Ganzhi from '../bagua/ganzhi.js'
import DunjiaStar from '../bagua/dunjiaStar.js'
import DunjiaDoor from '../bagua/dunjiaDoor.js'
import DunjiaGod from '../bagua/dunjiaGod.js'
import Younian from '../bagua/younian.js'

import BaseStage from './baseStage.js'
import Liuren from './liuren.js'
import Taiyi from './taiyi.js'

import Relation from '../relation/relation.js'

import dictionary from '../bagua/dictionary.js'

// 中5宫的索引，用于寄宫
const CENTER_PALACE_INDEX = 4
// 坤2宫的索引，用于寄宫
const EXTRA_PALACE_INDEX = 2

class Dunjia extends BaseStage {
  constructor(options = {}) {
    super(options)

    this.initDunjia()
  }
  
  initDunjia () {
    this.types.push('Dunjia')
    if (!this.enabled) {
      return
    }
        
    // 初始化九宫八卦等数据结构
    this.initStruct()
    
    // 移星换斗偏移量初始为0
    this.moveStarIndex = 0
    
    // 初始化起局干支
    this.initGanzhi()
    // 初始化阴阳、局数
    this.initYinyang()
    this.initNum()
    // 初始化旬首
    this.initXunHead()
    
    // 排地盘三奇六仪
    this.initGroundGan()
    // 排天盘三奇六仪
    this.initSkyGan()
    // 排八神
    this.initGods()
    // 排九星
    this.initStars()
    // 排八门
    this.initDoors()
    // 排隐干
    this.initOutGan()
    
    // 为中宫填充空对象
    this.palaces[CENTER_PALACE_INDEX].ele = {
      // 干支对象置为空对象
      groundGan: {},
      groundExtraGan: {},
      skyGan: {},
      skyExtraGan: {},
      // 单个对象置为null
      outGan: null,
      outExtraGan: null,
      god: null,
      star: null,
      door: null
    }
    
    // 排马星(定局干支驿马)
    this.timeGod.initKeyHorse(this.keyGanzhi)
    // 定空亡(定局干支空亡)
    this.timeGod.initKeyEmpty(this.keyGanzhi)
    
    // 基础找病，门迫、击刑、入墓
    this.findDiseaseBasic()
    
    // 设置排局完成后才能排布的神煞
    this.initAfterTimeGod()
  }
  
  // 初始化九宫、十二地支等结构
  initStruct () {
    // 初始化九宫八卦
    this.palaces = this.initPalaces()
    // 初始化地支十二神
    this.zhiPalaces = this.initZhiPalaces()
    
    // 联立九宫和地支
    this.connectZhiPalaces()
  }
  
  // 初始化起局干支
  initGanzhi () {
    // 虚函数，根据具体的遁甲局有所不同
    // 这里要获取到keyGanzhi，也就是定局的最后一个干支，一般时局为时干
    this.keyGanzhi = this.date.ganzhi[3]
  }
  
  // 初始化阴阳
  initYinyang () {
    this.isSolar = this.date.isSolar()
  }
  
  // 初始化局数
  initNum () {
    // 虚函数，在具体起局方式中重写
    this.num = 1
    this.countList = []
  }
  // 初始化旬首
  initXunHead () {
    this.xunHead = this.keyGanzhi.xun
  }
  
  // 排地盘三奇六仪
  initGroundGan () {
    const len = Dunjia.palacesBaguaList.length
    // 阳顺阴逆
    const offset = this.isSolar ? len : (-1) * len
    const start = Dunjia.getIndexByAfter(this.num)
    this.travePalaceByAfterNum(start, offset, (item, index) => {
      const gan = Gan.liuyiList[index]
      const groundGan = new Ganzhi(gan, null, this.xunHead.index, {
        description: item.description + Dunjia.dictionary.GROUNDGAN
      })
      item.ele.groundGan = groundGan
      item.ele.groundExtraGan = {}
      
      // 地盘天干与旬首天干相同时，确定值符值使门
      if (this.xunHead.head.name === groundGan.gan.name) {
        // 找到值符值使门，依据是该宫位的后天八卦数
        this.initHeadStarDoor(item.afterNum)
        // 保存地盘旬首落宫
        this.xunHeadGroundIndex = item.order
        // 定局天干(时干)为甲时，还要保存值符落宫
        if (this.keyGanzhi.gan.index === 0) {
          this.headStarIndex = item.order
        }
      }
      
      // 当时干不为甲时，地盘天干与时干相同时，保存值符落宫
      if (this.keyGanzhi.gan.name === groundGan.gan.name) {
        this.headStarIndex = item.order
      }
      
      return item
    })
    
    // 遍历完成后，中五宫寄于坤二宫
    const ganIndex = this.palaces[CENTER_PALACE_INDEX].ele.groundGan.gan.index
    const zhiIndex = this.palaces[CENTER_PALACE_INDEX].ele.groundGan.zhi.index
    this.palaces[EXTRA_PALACE_INDEX].ele.groundExtraGan = new Ganzhi(ganIndex, zhiIndex, null, {
      description: this.palaces[EXTRA_PALACE_INDEX].description + Dunjia.dictionary.GROUNDGAN + Dunjia.dictionary.EXTRAGAN
    })
    this.palaces[CENTER_PALACE_INDEX].ele = {}
  }
  
  // 确定值符值使门
  initHeadStarDoor (num) {
    const starIndex = DunjiaStar.afterToIndex(num)
    const doorIndex = DunjiaDoor.afterToIndex(num)
    // 值符值使是否为 天禽星/中门
    this.isSpecialStar = starIndex === DunjiaStar.starList.length - 1 ? true : false
    
    this.headStarDoor = {
      star: new DunjiaStar(starIndex, {
        description: Dunjia.dictionary.HEADSTAR
      }),
      door: new DunjiaDoor(doorIndex, {
        description: Dunjia.dictionary.HEADDOOR
      })
    }
  }
  
  // 排天盘三奇六仪
  initSkyGan () {
    const skyStart = Dunjia.fixedIndex(this.headStarIndex)
    // 地盘索引开始
    let curGroundIndex = Dunjia.fixedIndex(this.xunHeadGroundIndex)
    
    // 遍历四周八个宫
    const len = Dunjia.palacesBaguaList.length - 1
    this.travePalaceByClock(skyStart, len, (item, index, list) => {
      // 地盘干赋予天盘干
      const groundGanzhi = list[curGroundIndex].ele.groundGan
      item.ele.skyGan = new Ganzhi(groundGanzhi.gan.index, groundGanzhi.zhi.index, null, {
        description: item.description + Dunjia.dictionary.SKYGAN
      })
      item.ele.skyExtraGan = {}
      
      // 地盘寄宫干赋予天盘寄宫
      const groundExtraGanZhi = list[curGroundIndex].ele.groundExtraGan
      if (groundExtraGanZhi.gan) {
        item.ele.skyExtraGan = new Ganzhi(groundExtraGanZhi.gan.index, groundExtraGanZhi.zhi.index, null, {
          description: item.description + Dunjia.dictionary.SKYGAN + Dunjia.dictionary.EXTRAGAN
        })
      }
      
      // 地盘索引前进
      curGroundIndex = list[curGroundIndex].pointer.go
      
      return item
    })
  }
  
  // 排八神
  initGods () {
    // 从天盘旬首位置开始排八神
    const skyStart = Dunjia.fixedIndex(this.headStarIndex)
    
    // 遍历四周八个宫，阳顺阴逆
    const len = Dunjia.palacesBaguaList.length - 1
    const offset = this.isSolar ? len : (-1) * len
    this.travePalaceByClock(skyStart, offset, (item, index) => {
      const god = new DunjiaGod(index, {
        description: item.description + Dunjia.dictionary.DUNJIA_GOD
      })
      item.ele.god = god
      return item
    })
  }
  
  // 排九星
  initStars () {
    // 从天盘旬首位置开始排九星
    const skyStart = Dunjia.fixedIndex(this.headStarIndex)
    let starIndex = this.headStarDoor.star.index
    
    // 遍历四周八个宫，永远顺排
    const len = Dunjia.palacesBaguaList.length - 1
    this.travePalaceByClock(skyStart, len, (item, index) => {
      const star = new DunjiaStar(starIndex, {
        description: item.description + Dunjia.dictionary.DUNJIA_STAR
      })
      item.ele.star = star
      starIndex = star.nextIndex(1, this.isSpecialStar)
      return item
    })
  }
  
  // 排八门
  initDoors () {
    // 计算值使门所在的后天宫宫位数
    const offset = this.isSolar ? this.keyGanzhi.gan.index : (-1) * this.keyGanzhi.gan.index
    // 根据定局干支与旬首进行偏移
    let doorStartNum = Dunjia.getOffsetPalaceNum(this.headStarDoor.door.afterOrigin, offset)
    // 转换为宫位索引
    let doorStartIndex = Dunjia.getIndexByAfter(doorStartNum)
    // 记录值使门所在宫位索引
    this.headDoorIndex = doorStartIndex
    
    // 处理中5宫寄坤2宫的情况
    doorStartIndex = Dunjia.fixedIndex(doorStartIndex)

    // 获取值使门索引
    let doorIndex = this.headStarDoor.door.index
    // 遍历四周八个宫，永远顺排
    const len = Dunjia.palacesBaguaList.length - 1
    this.travePalaceByClock(doorStartIndex, len, (item, index) => {
      const door = new DunjiaDoor(doorIndex, {
        description: item.description + Dunjia.dictionary.DUNJIA_DOOR
      })
      item.ele.door = door
      doorIndex = door.nextIndex(1, this.isSpecialStar)
      return item
    })
  }
  
  // 排隐干
  initOutGan () {
    // 获取地盘时干落宫的完整地盘
    const ganStartIndex = Dunjia.fixedIndex(this.headStarIndex)
    const ganStartPalace = this.palaces[ganStartIndex]
    let checkText = ganStartPalace.ele.groundGan.gan.name
    if (ganStartPalace.ele.groundExtraGan.gan) {
      checkText = checkText + ganStartPalace.ele.groundExtraGan.gan.name
    }
    
    // 获取值使门落宫的天地盘
    const headDoorIndex = Dunjia.fixedIndex(this.headDoorIndex)
    const headDoorPalace = this.palaces[headDoorIndex]
    let doorSkyText = headDoorPalace.ele.skyGan.gan.name
    if (headDoorPalace.ele.skyExtraGan.gan) {
      doorSkyText = doorSkyText + headDoorPalace.ele.skyExtraGan.gan.name
    }
    let doorGroundText = headDoorPalace.ele.groundGan.gan.name
    if (headDoorPalace.ele.groundExtraGan.gan) {
      doorGroundText = doorGroundText + headDoorPalace.ele.groundExtraGan.gan.name
    }
    
    
    // 第一种情况，地盘时干与值使门落宫的天盘、地盘均不相等，绕一圈
    if (checkText !== doorSkyText && checkText !== doorGroundText) {
      // 地盘索引
      let curGroundIndex = ganStartIndex
      const len = Dunjia.palacesBaguaList.length - 1
      this.travePalaceByClock(headDoorIndex, len, (item, index, list) => {
        const groundGanzhi = list[curGroundIndex].ele.groundGan
        item.ele.outGan = new Gan(groundGanzhi.gan.index, {
          description: item.description + Dunjia.dictionary.OUTGAN
        })
        item.ele.outExtraGan = null
        
        // 地盘寄宫干赋予隐干寄宫
        const groundExtraGanZhi = list[curGroundIndex].ele.groundExtraGan
        if (groundExtraGanZhi.gan) {
          item.ele.outExtraGan = new Gan(groundExtraGanZhi.gan.index, {
            description: item.description + Dunjia.dictionary.OUTGAN + Dunjia.dictionary.EXTRAGAN
          })
        }
        
        // 地盘索引前进
        curGroundIndex = list[curGroundIndex].pointer.go
        
        return item
      })
    }
    else {
      // 隐干逢同求变，准备入中宫
      // 获取起始六仪
      let liuyiIndex = Gan.liuyiList.indexOf(this.keyGanzhi.gan.name)
      
      // 定局干支的天干为甲时的处理，用旬首干起始
      if (this.keyGanzhi.gan.index == 0) {
        liuyiIndex = Gan.liuyiList.indexOf(this.xunHead.head.name)
      }
      
      // 特殊情况，隐干逢同，以时干入中时，发现再次重复(即时干落中宫)，则改为坤宫地盘(非中宫寄宫的地盘天干)入中
      if (this.headStarIndex == CENTER_PALACE_INDEX) {
        liuyiIndex = Gan.liuyiList.indexOf(this.palaces[EXTRA_PALACE_INDEX].ele.groundGan.gan.name)
      }
      
      const liuyiLen = Gan.liuyiList.length
      const offset = this.isSolar ? liuyiLen : (-1) * liuyiLen
      this.travePalaceByAfterNum(CENTER_PALACE_INDEX, offset, (item, index) => {
        item.ele.outGan = new Gan(Gan.liuyiList[liuyiIndex], {
          description: item.description + Dunjia.dictionary.OUTGAN
        })
        item.ele.outExtraGan = null
        
        liuyiIndex = (liuyiIndex + 1) % liuyiLen
        return item
      })
      
      // 遍历完成后，中五宫寄于坤二宫
      const ganIndex = this.palaces[CENTER_PALACE_INDEX].ele.outGan.index
      this.palaces[EXTRA_PALACE_INDEX].ele.outExtraGan = new Gan(ganIndex, {
        description: this.palaces[EXTRA_PALACE_INDEX].description + Dunjia.dictionary.OUTGAN + Dunjia.dictionary.EXTRAGAN
      })
      this.palaces[CENTER_PALACE_INDEX].ele = {}
    }
  }
  
  // 九宫找病基础版，门迫，击刑，入墓
  findDiseaseBasic () {
    // 遍历四周八个宫
    const len = Dunjia.palacesBaguaList.length - 1
    this.travePalaceByClock(0, len, (item, index) => {
      let objs = [item.ele.door, item.ele.skyGan.gan, item.ele.groundGan.gan]
      // 补充寄宫符号
      if (item.ele.groundExtraGan.gan) {
        objs.push(item.ele.groundExtraGan.gan)
      }
      if (item.ele.skyExtraGan.gan) {
        objs.push(item.ele.skyExtraGan.gan)
      }
      if (item.ele.god.name === '值符') {
        objs.push(item.ele.god)
      }
      const keyObjs = [item.ele.bagua]
      const methods = ['doorBroken', 'dunjiaXing', 'dunjiaToob']
      item.basicRelation = new Relation(objs, methods, keyObjs)
      return item
    })
  }
  
  // 移星换斗
  moveStar (index = 0) {
    const len = Dunjia.palacesBaguaList.length - 1
    const offset = index >= this.moveStarIndex ? index - this.moveStarIndex : (index + len) - this.moveStarIndex
    
    // 先确定起点
    let curIndex = 0
    this.travePalaceByClock(0, len, (item, index) => {
      if (index === offset) {
        curIndex = item.order
        return false
      }
    })
    
    // 移星换斗操作
    this.travePalaceByClock(0, len, (item, index, list) => {
      let des = list[curIndex]
      
      const temp = {
        // 干支对象置为空对象
        groundGan: item.ele.groundGan,
        groundExtraGan: item.ele.groundExtraGan,
        skyGan: item.ele.skyGan,
        skyExtraGan: item.ele.skyExtraGan,
        god: item.ele.god,
        star: item.ele.star,
        door: item.ele.door
      }
      // 缓存起来
      des.ele.moveStarTempCache = temp
      
      curIndex = des.pointer.go
    })
    
    // 更新赋值
    this.travePalaceByClock(0, len, (item, index) => {
      item.ele.groundGan = item.ele.moveStarTempCache.groundGan
      item.ele.groundExtraGan = item.ele.moveStarTempCache.groundExtraGan
      item.ele.skyGan = item.ele.moveStarTempCache.skyGan
      item.ele.skyExtraGan = item.ele.moveStarTempCache.skyExtraGan
      item.ele.god = item.ele.moveStarTempCache.god
      item.ele.star = item.ele.moveStarTempCache.star
      item.ele.door = item.ele.moveStarTempCache.door
      // 删除缓存
      item.ele.moveStarTempCache = null
      delete item.ele.moveStarTempCache
      return item
    })
    
    // 更新移星换斗索引
    this.moveStarIndex = index
    
    // 重新找病
    this.findDiseaseBasic()
  }
  
  /* @section 外圈神煞排布方法 */
  // 太乙
  setTaiyi () {
    // 暂时只支持时局穿太乙
    if (this.options.type !== 'timeDunjia') {
      return
    }
    
    // 计算太乙积数
    Taiyi.countTaiyiNum(this, this.keyGanzhiIndex)
    // 排太乙
    Taiyi.setTaiyi(this)
    // 排文昌
    Taiyi.setWenchang(this)
    // 排计神
    Taiyi.setJishen(this)
    // 排始击
    Taiyi.setShiji(this)
    // 排算数、大将、参将
    Taiyi.setJiang(this)
    // 排四神、天乙、地乙、飞符
    Taiyi.setFourGods(this)
    // 排五福
    Taiyi.setWufu(this)
    // 排小游
    Taiyi.setXiaoyou(this)
    // 排三基
    Taiyi.setThreeBases(this)
  }
  
  
  // 外圈神煞
  setOuterGod (god, extra) {
    switch (god) {
      // 时局部分神煞
      case 'yuejiang':
        return this.setYuejiang(god, extra)
        break
      case 'jianchu':
        return this.setJianchu(god, extra)
        break
      case 'twelvePalace':
        return this.setTwelvePalaces(god, extra)
        break
      case 'guiGod':
        return this.setGuiGods(god, extra)
        break
      case 'taiyin':
        return this.setTaiyin(god, extra)
        break
      case 'liurenGan':
        return this.setLiurenGan(god, extra)
        break
      case 'bigYounian':
        return this.setBigYounian(god, extra)
        break
      // 以下为山向的部分神煞
      case 'twelveState':
        return this.setTwelveState(god, extra)
        break
      case 'otherPosGod':
        return this.setOtherPosGod(god, extra)
        break
      case 'jiangGod':
        return this.setJiangGod(god, extra)
        break
      case 'relativeBagua':
        return this.setRelativeBagua(god, extra)
        break
      case 'smallYounian':
        return this.setSmallYounian(god, extra)
        break
      default:
    }
    return {}
  }
  
  // 排月将，并返回引导信息
  setYuejiang (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    Liuren.setYuejiang(this, extra, eleKey)
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: [
        {
          text: '月将',
          bagua: this.date.yuejiang
        },
      ]
    }
  }
  
  // 排十二建神
  setJianchu (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    Liuren.setJianchu(this, extra, eleKey)
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排十二宫
  setTwelvePalaces (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    Liuren.setTwelvePalaces(this, extra, eleKey)
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排十二神将
  setGuiGods (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    
    let typeList = extra.split(' ')
    // 先区分阴阳贵人
    if (typeList[0] === 'auto') {
      const timeZhiIndex = this.date.ganzhi[3].zhi.index
      if (timeZhiIndex >= 3 && timeZhiIndex <= 8) {
        // 卯酉时分定阴阳，卯到申为阳
        typeList[0] = 'solar'
      }
      else {
        typeList[0] = 'lunar'
      }
    }
    const yinyangIndex = typeList[0] === 'solar' ? 0 : 1
    
    // 直接找到贵人
    let ganzhi = Dunjia.getGanzhiByTimeKey(this, typeList[1])
    if (!ganzhi) {
      // 没有合适的值
      return {}
    }
    // 获取贵人对象列表
    const guiGodList = this.timeGod.guiGod(ganzhi)
    const guiGod = guiGodList[yinyangIndex]
    
    // 排贵神
    const newExtra = typeList.join(' ')
    Liuren.setGuiGods(this, newExtra, eleKey)
    
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: [
        {
          text: yinyangIndex ? '阴贵人' : '阳贵人',
          bagua: guiGod
        }
      ]
    }
  }
  
  // 排太阴
  setTaiyin (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    Liuren.setTaiyin(this, extra, eleKey)
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: [
        {
          text: '太阴',
          bagua: this.timeGod.taiyin
        }
      ]
    }
  }
  
  // 排天干
  setLiurenGan (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    Liuren.setLiurenGan(this, extra, eleKey)
    // 返回引导信息
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排大游年
  setBigYounian (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    if (!this.palaces[0].ele.hasOwnProperty(eleKey)) {
      const srcIndex = parseInt(extra)
      const len = Dunjia.palacesBaguaList.length - 1
      this.travePalaceByClock(0, len, (item, index, list) => {
        item.ele[eleKey] = new Younian([list[srcIndex].ele.bagua, item.ele.bagua], {
          description: item.description + list[srcIndex].description + dictionary.BIG_YOUNIAN
        })
        return item
      })
    }
    
    return {
      key: eleKey,
      divide: 8,
      extraInfo: []
    }
  }
  
  // 排十二长生，山向专有方法，这里几乎为没有实质意义的虚函数
  setTwelveState (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排山向其他零散神煞，山向常用方法，这里几乎为没有实质意义的虚函数
  setOtherPosGod (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排山向的将神，山向的常用方法，这里几乎为没有实际意义的虚函数
  setJiangGod (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 排山向的关联八卦，山向的常用方法，这里几乎为没有实际意义的虚函数
  setRelativeBagua (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  // 小游年，山向用，虚函数
  setSmallYounian (god, extra) {
    const eleKey = Dunjia.getOuterKey(god, extra)
    return {
      key: eleKey,
      divide: 12,
      extraInfo: []
    }
  }
  
  /* @section static方法 */
  // 根据后天八卦数获取索引
  static getIndexByAfter (num) {
    const index = Dunjia.palacesAfterNumList.indexOf(num)
    return index >= 0 ? index : 0
  }
  
  // 后天八卦数前进或者后退
  static getOffsetPalaceNum (num, offset = 0) {
    const len = Dunjia.palacesBaguaList.length
    return (num + len + (offset % len) - 1) % len + 1
  }
  
  // 中五宫寄于坤二宫，获取修正后的索引
  static fixedIndex (index) {
    return index === CENTER_PALACE_INDEX ? EXTRA_PALACE_INDEX : index
  }
}

Object.assign(Dunjia, {})

export default Dunjia